/*
 * Things are geting worse these days, I'm trying to start all over again
 * The porpose of this test demo is that draw a block with all effects below:
 * 1.   Light               #Done
 * 2.   Shadow
 * 3.   Texture             #Done
 * 4.   ArchBall with zoom  #Done
 * 5.   PixMap
 * 6.   Reflection Effect   
 * 7.   Motion Blur
 * 8.   HDR(High Dynamic Range) light effect
 * All of these target may not build in a very well orgnised form, since this
 * is only a test demo for all of those stuffs above, good luck!
 */
#include <GL/glut.h>
#include <stdio.h>
#include <gvector3d.h>
#include <gmatrix3d.h>
#include <ggltools.h>
#include <IL/ilu.h>
#include <IL/il.h>
#include <iostream>
using namespace std;
//archBall values
// Êó±êÎ»ÖÃ 
int gMouseX = -1;
int gMouseY = -1;

// ÊÇ·ñ½øÐÐÊó±ê×·×ÙÇò 
bool gIsStartTrackBall = false;

// Ðý×ª¾ØÕó 
GMatrix3d gRotMatrix;


const GLfloat CUBE_SIZE = 20.0f;
const GLfloat VERTEX_ARRAY[][3] = 
{
    -CUBE_SIZE, CUBE_SIZE, CUBE_SIZE,
    -CUBE_SIZE, -CUBE_SIZE, CUBE_SIZE,
    CUBE_SIZE, -CUBE_SIZE, CUBE_SIZE,
    CUBE_SIZE, CUBE_SIZE, CUBE_SIZE,

    -CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE,
    -CUBE_SIZE, CUBE_SIZE, -CUBE_SIZE,
    CUBE_SIZE, -CUBE_SIZE, -CUBE_SIZE,
    CUBE_SIZE, CUBE_SIZE, -CUBE_SIZE
};
//define wheel
const GLuint GLUT_WHEEL_UP = 3;
const GLuint GLUT_WHEEL_DOWN = 4;
GLfloat zoom = -150.0f;


const int VERTEX_INDEX[] = 
{
    //face 1
    0, 1, 2, 3,
    //face 2
    3, 2, 6, 7,
    //face 3
    4, 1, 0, 5,
    //4
    6, 4, 5, 7,
    //5
    3, 7 ,5, 0,
    1, 4, 6, 2
};

const GLfloat NORMAL_ARRAY[][3] = 
{
    0.0f, 0.0f, 1.0f,
    1.0f, 0.0f, 0.0f,
    -1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, -1.0f,
    0.0f, 1.0f, 0.0f,
    0.0f, -1.0f, 0.0f

};

const GLfloat TEXTURE_ARRAY[][2] =
{
    0.0f, 1.0f,
    1.0f, 1.0f,
    1.0f, 0.0f,
    0.0f, 0.0f
};
    



//texture
static GLint imageWidth = 64;
static GLint imageHeight = 64;
static GLint pixelLength;
static unsigned char *pixelData;
static GLuint texName;


/*
void loadImg(char *bmpName)
{
    FILE *pFile = fopen(bmpName, "rb");
    if (pFile == 0)
        exit(0);        //no file fond
    fseek(pFile, 0x0012, SEEK_SET);     //find w
    fread(&imageWidth, sizeof(imageWidth), 1, pFile);
    fread(&imageHeight, sizeof(imageHeight), 1, pFile);
    pixelLength = imageWidth * 3;
    while(pixelLength % 4 != 0)
        ++pixelLength;
    pixelLength *= imageHeight;
    pixelData = (GLubyte*)malloc(pixelLength);
    if(pixelData == 0)
        exit(0);
    fseek(pFile, 54, SEEK_SET);
    fread(pixelData, pixelLength, 1, pFile);
    fclose(pFile);
    //debug
    //for( int i=0; i < pixelLength; i++)
     //   cout<<pixelData[i]<<" ";
    cout<<sizeof(GLubyte)<<"!!!";
}*/
//load image with DevIL
void loadImg(char *fileName)
{
    ilInit();
    iluInit();
    ILuint imgId = 0;
    ilGenImages(1, &imgId);
    ilBindImage(imgId);

    ilLoadImage(fileName);
    imageWidth = ilGetInteger(IL_IMAGE_WIDTH);
    imageHeight = ilGetInteger(IL_IMAGE_HEIGHT);

    unsigned int size = imageWidth * imageHeight * 4;  
    pixelData = new unsigned char[size];  
    ilCopyPixels(0, 0, 0, imageWidth, imageHeight, 1, IL_RGBA, IL_UNSIGNED_BYTE, pixelData);   

    ilBindImage(0);
    ilDeleteImage(imgId);
}

GVector3d gMousePtToSphereVec(int x, int y, int w, int h)
{
    GVector3d vec;
    vec.setX((2.0*x-w)/w);
    vec.setY((h-2.0*y)/h);
    double r=vec.x()*vec.x()+vec.y()*vec.y();
    if(r>1)r=1;
    vec.setZ(sqrt(1-r));
    vec.normalize();
    return vec;
}

void init() //init function
{
     //Light values and coordinates
     GLfloat ambientLight[] = { 0.3f, 0.3f, 0.3f, 1.0f };
     GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.7f, 1.0f };
     GLfloat lightPos[] = {20.0f, 20.0f, 50.0f};
     glEnable(GL_DEPTH_TEST);
     glFrontFace(GL_CCW);
     glEnable(GL_CULL_FACE);
     glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
     // Hidden surface removal
     // Counterclockwise polygons face out
     // Do not calculate inside of jet
     // Enable lighting
     glEnable(GL_LIGHTING);
     // Set up and enable light 0
     glLightfv(GL_LIGHT0,GL_AMBIENT,ambientLight);
     glLightfv(GL_LIGHT0,GL_DIFFUSE,diffuseLight);
     glEnable(GL_LIGHT0);
     // Enable color tracking
     glEnable(GL_COLOR_MATERIAL);
     // Set material properties to follow glColor values
     glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);

     glClearColor(0.2f, 0.2f, 0.2f, 3.0f );

     glClearAccum(0.0, 0.0, 0.0, 1.0);
    
     //Rescale normals to unit length
     glEnable(GL_NORMALIZE);
     glLightfv(GL_LIGHT0,GL_POSITION,lightPos);

     //load texture
     char picName[] = "test.png" ;
     glGenTextures(1, &texName);
     glPixelStorei(GL_TEXTURE_2D, 4);
     glBindTexture(GL_TEXTURE_2D, texName);
     loadImg(picName);

     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imageWidth, imageHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixelData);
     /*
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    */
     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR);

     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
     glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);


     glEnable(GL_TEXTURE_2D);
     
     glShadeModel(GL_FLAT);
     //motion blur
     glutPostRedisplay();
     glClear(GL_ACCUM_BUFFER_BIT);
     glAccum(GL_ACCUM, 1.0f);


}

//draw plane
void drawWirePlane(GLfloat width)
{
    glColor3f(0.6f, 0.6f, 0.6f);
    for(float u=-width; u <= width; u+=width/50)
    {
        glBegin(GL_LINES);
        glVertex3f(-width, 0, u);
        glVertex3f(width, 0, u);
        
        glVertex3f(u, 0, -width);
        glVertex3f(u, 0, width);
        glEnd();
    }
}

void drawPlane(GLfloat width)
{
    glColor3f(0.1f, 0.1f, 0.1f);
    glBegin(GL_QUADS);
        glNormal3f(0.0f, 1.0f, 0.0f);
        glVertex3f(width, 0.0f, width);
        glVertex3f(width, 0.0f, -width);
        glVertex3f(-width, 0.0f, -width);
        glVertex3f(-width, 0.0f, width);
    glEnd();
}
        

void display()  //display function
{

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    //glRotatef(-45.0, 1.0f, 0.0f, 1.0f);
    gluLookAt(0.0f, 40.0f, zoom, 0.0f, 40.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    drawWirePlane(200.0f);
    //draw sphere
    glPushMatrix();
    glColor3f(0.9f, 0.4f, 0.4f);
    glTranslatef(-300.0f, 40.0f, 300.0f);
    for(int i = 0; i < 10; i++)
    {
        glTranslatef(80.0f, 0.0f, 0.0f);
        glutSolidSphere(20.0f, 60, 60);
    }
    glPopMatrix();
    //-----------------

    glPushMatrix();
    glTranslatef(0.0f, 40.0f, 0.0f);
    glMultMatrixd(gRotMatrix);
    glColor3f(1.0f, 1.0f, 1.0f);
    glBegin(GL_QUADS);
        for(int i = 0; i < 6; i++)         //6 faces
        {
            glNormal3fv(NORMAL_ARRAY[i]);   //define normal of each face
            for(int j = 0; j < 4; j++)      //4 vertex on each face
            {
                
                glTexCoord2fv(TEXTURE_ARRAY[j]);    //texture coordinate
                glVertex3fv(VERTEX_ARRAY[VERTEX_INDEX[i*4+j]]); 
            }
        }
    glEnd();
    glColor3f(0.5f, 0.1f, 0.5f);
    glutWireCube(50.0f);

    glPopMatrix();

    //motion blur
    glAccum(GL_ACCUM, 0.10f);
    glAccum(GL_MULT, 0.90f);
    glAccum(GL_RETURN, 1.0f);

    glFlush();
    glutSwapBuffers();

}
void mouseMoveEvent(int x, int y)
{
    if(gIsStartTrackBall)
    {
        if(x!=gMouseX||y!=gMouseY)
        {
            int w=glutGet(GLUT_WINDOW_WIDTH);
            int h=glutGet(GLUT_WINDOW_HEIGHT);
            GVector3d lastVec,currentVec,axis;
            double rotAngle;
            lastVec=gMousePtToSphereVec(gMouseX,h-gMouseY,w,h);
            currentVec=gMousePtToSphereVec(x,h-y,w,h);
            axis=lastVec.crossMult(currentVec);
            axis.normalize();
            rotAngle=acos(lastVec.dotMult(currentVec));
            rotAngle*=RADIAN_TO_ANGLE;
            gMouseX=x;gMouseY=y;

         //¿¼ÊÔÌâ
            GMatrix3d m=GMatrix3d::createRotate(rotAngle,axis);
            gRotMatrix *=m;

            //glutPostRedisplay();
        }

    }
}        

// Êó±êÊÂ¼þº¯Êý 
void mouseEvent(int button, int state, int x, int y)
{
    if(button == GLUT_LEFT_BUTTON)
    {
        if(state == GLUT_DOWN)
        {
            gMouseX = x;
            gMouseY = y;
            gIsStartTrackBall = true;
        }
        else if(state == GLUT_UP)
        {    
            gIsStartTrackBall = false;
        }            
        //glutPostRedisplay();
    }
    //process wheel to zoom in and zoom out
    //if ( zoom <= -100 && zoom >= -1000)
    {
    if (state == GLUT_UP && button == GLUT_WHEEL_UP)
    {
        zoom += 3.0f;
    //    glutPostRedisplay();
    }
    if (state == GLUT_UP && button == GLUT_WHEEL_DOWN)
    {
        zoom -= 3.0f;
        //glutPostRedisplay();
    }
    }
}       


void changeSize(int w, int h)
{
    GLfloat aspectRatio;
    glClearAccum(0.0, 0.0, 0.0, 1.0);
    glClear(GL_ACCUM_BUFFER_BIT);


    //prevent a divide by zero
    if(h==0)
        h = 1;
    //Set Viewport
    glViewport(0, 0, w, h);

    //reset coordinate system
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    //Establish clipping voluem(left, right, bottom, top, near, far)
    aspectRatio = (GLfloat)w / (GLfloat)h;
    /*
    if(w<=h)
        glOrtho(-100.0, 100.0, -100 / aspectRatio, 100.0 * aspectRatio, 100.0, -100.0);
    else
        glOrtho(-100.0 * aspectRatio, 100.0 * aspectRatio, -100.0, 100.0, 100.0, -100.0);
        */
    gluPerspective(45.0f, aspectRatio, 1.0f, 500.0f);    //using perspective
    
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
void timerFunction(int value)       
    //force it to refresh, no need of postRedisplay function any more
{
    //change values here
    
    glutPostRedisplay();
    glutTimerFunc(10,timerFunction,1);
}


int main( int argc, char **argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    glutInitWindowPosition(100, 100);
    glutCreateWindow("cubeDemo");
    glutDisplayFunc(display);
    glutReshapeFunc(changeSize);
    glutTimerFunc(10, timerFunction, 1);
    
    glutMouseFunc(mouseEvent);
    glutMotionFunc(mouseMoveEvent);
    init();
    

    glutMainLoop();

    return 0;
}

